package cn.com.joyplus.wechat.service;

import cn.com.joyplus.wechat.configuration.CacheConfig;
import cn.com.joyplus.wechat.model.JsdkConfig;
import cn.com.joyplus.wechat.model.WechatContext;
import cn.com.joyplus.wechat.utils.Constant;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.qq.weixin.mp.aes.AesException;
import com.qq.weixin.mp.aes.WXBizMsgCrypt;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Formatter;
import java.util.HashMap;
import java.util.Map;

/**
 * Created by 王灏川(wangrs@qq.com) on 2017/8/11 0011.
 */
@Service
@SuppressWarnings("unchecked")
public class WechatCoreApiService {
    private static Logger logger = LoggerFactory.getLogger(WechatCoreApiService.class);

    private final String ACCESS_TOKEN_URL = String.format("%s/cgi-bin/token", Constant.WECHAT_API_BASE_URL);

    private final String JS_API_TICKET_URL =  String.format("%s/cgi-bin/ticket/getticket?access_token=%%s&type=jsapi", Constant.WECHAT_API_BASE_URL);


    private final String ACCESS_TOKEN_KEY = "access_token";

    private final String JS_API_TICKET_KEY = "ticket";

    private final String ACCESS_TOKEN_ERROR = "errcode";

    private final String ACCESS_TOKEN_ERRMSG = "errmsg";


    @Value("${wechat.jsdk.debug:false}")
    private Boolean jsDebugMode;

    @Autowired
    private RemoteResourceService remoteResourceService;


    @Autowired
    private WechatContext wechatContext;


    @Cacheable(value = CacheConfig.CACHE_FOR_ACCESSTOKEN, key = "'wxAccessToken'", unless = "#result == null")
    public String getWechatAccessToken() {
        logger.info("Get Wechat Access Token.");

        Map<String, String> param = new HashMap<String, String>();
        param.put("grant_type", "client_credential");
        param.put("appid", wechatContext.getAppId());
        param.put("secret", wechatContext.getAppSecret());
        try {
            String accessToken = remoteResourceService.getRemoteResource(this.ACCESS_TOKEN_URL, param, "GET");
            ObjectMapper mapper = new ObjectMapper();
            Map<String, String> result = mapper.readValue(accessToken, HashMap.class);

            if (result.containsKey(ACCESS_TOKEN_KEY)) {
                accessToken = result.get(ACCESS_TOKEN_KEY).toString();
                logger.info(String.format("Renew weChat access token success:%s", accessToken));
                return accessToken;
            } else if (result.containsKey(ACCESS_TOKEN_ERROR) && result.containsKey(ACCESS_TOKEN_ERRMSG)) {
                logger.info(String.format("Renew wechat access token failed:%s:%s", result.get(ACCESS_TOKEN_ERROR), result.get(ACCESS_TOKEN_ERRMSG)));
            } else {
                logger.info("Error json format,renew wechat access token failed");
            }
        } catch (Exception e) {
            logger.error(e.getMessage());
        }

        return null;
    }

    @Cacheable(value = CacheConfig.CACHE_FOR_ACCESSTOKEN, key = "'jsapiTicket'", unless = "#result == null")
    public String getJsApiTicket(String accessToken) {
        logger.info("Get js api ticket.");

        try {
            String jsapiTicket = remoteResourceService.getRemoteResource(String.format(this.JS_API_TICKET_URL, accessToken));
            ObjectMapper mapper = new ObjectMapper();
            Map<String, String> result = mapper.readValue(jsapiTicket, HashMap.class);

            if (result.containsKey(JS_API_TICKET_KEY)) {
                jsapiTicket = result.get(JS_API_TICKET_KEY).toString();
                logger.info(String.format("Renew js api ticket success:%s", jsapiTicket));
                return jsapiTicket;
            }

        } catch (Exception e) {
            logger.error(e.getMessage());
        }

        return null;
    }

    public JsdkConfig getJsdkConfig(String jsapi_ticket, String url) {
        JsdkConfig jsdkConfig = new JsdkConfig(wechatContext.getAppId(), jsDebugMode);
        //注意这里参数名必须全部小写，且必须有序
        String string1 = "jsapi_ticket=" + jsapi_ticket +
                "&noncestr=" + jsdkConfig.getNonceStr() +
                "&timestamp=" + jsdkConfig.getTimestamp() +
                "&url=" + url;
        try {
            MessageDigest crypt = MessageDigest.getInstance("SHA-1");
            crypt.reset();
            crypt.update(string1.getBytes("UTF-8"));
            jsdkConfig.setSignature(byteToHex(crypt.digest()));
        } catch (NoSuchAlgorithmException e) {
            logger.error(e.getMessage());
        } catch (UnsupportedEncodingException e) {
            logger.error(e.getMessage());
        }

        return jsdkConfig;
    }

    public boolean checkSignature(String signature, String timestamp, String nonce) {
        if (null == signature || null == timestamp || null == nonce) {
            return false;
        }
        String[] arr = new String[]{wechatContext.getToken(), timestamp, nonce};
        Arrays.sort(arr);
        StringBuilder content = new StringBuilder();
        for (int i = 0; i < arr.length; i++) {
            content.append(arr[i]);
        }
        MessageDigest md = null;
        String tmpStr = null;

        try {
            md = MessageDigest.getInstance("SHA-1");
            byte[] digest = md.digest(content.toString().getBytes());
            tmpStr = byteToHex(digest);
        } catch (NoSuchAlgorithmException e) {
            logger.error(e.getMessage(), e);
        }

        return tmpStr != null ? tmpStr.toUpperCase().equals(signature.toUpperCase()) : false;
    }

    public String DecryptMsg(String signature, String timestamp, String nonce, String msg) {

        try {
            WXBizMsgCrypt pc = new WXBizMsgCrypt(wechatContext.getToken(), wechatContext.getEncodingAESKey(), wechatContext.getAppId());

            return pc.decryptMsg(signature, timestamp, nonce, msg);
        } catch (AesException e) {
            logger.error(e.getMessage(), e);

        }

        return msg;
    }

    public String EncryptMsg(String msg) {
        //todo:加密发出的微信消息
        return null;
    }


    private String byteToHex(final byte[] hash) {
        Formatter formatter = new Formatter();
        for (byte b : hash) {
            formatter.format("%02x", b);
        }
        String result = formatter.toString();
        formatter.close();
        return result;
    }

}
